/**
 * Copyright 2014,Peak Tai 台俊峰(taijunfeng_it@sina.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * 		http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.cenluan.mi;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.cenluan.common.Const;
import com.cenluan.common.Profiles;
import com.cenluan.ext.jfinal.PkController;
import com.cenluan.kit.UUIDKit;
import com.cenluan.login.ClearLoginInterceptor;
import com.cenluan.session.SessionManager;
import com.cenluan.user.User;
import com.cenluan.user.UserKit;
import com.jfinal.kit.HttpKit;
import com.jfinal.log.Logger;

/**
 * 小米controller，完成与小米有关的业务，主要是利用Auth2.0接口做登录。<br>
 * Auth2.0文档地址：http://dev.xiaomi.com/doc/?p=810
 * 
 * @author peak
 * 
 */
public class MiController extends PkController {
	/**
	 * auth授权接口的参数值，跳转链接时放入session，回调时验证
	 */
	private static final String SESSION_STATE = "MI_STATE";

	private Logger logger = Logger.getLogger(this.getClass());

	/**
	 * 跳转，去登录或者绑定
	 * 
	 * @throws UnsupportedEncodingException
	 */
	@ClearLoginInterceptor
	public void forward() throws UnsupportedEncodingException {
		String redirectUri = URLEncoder.encode(getUrlPrefix() + "/mi/login", "UTF-8");
		if ("bind".equals(getPara("to"))) {
			redirectUri = URLEncoder.encode(getUrlPrefix() + "/mi/bind", "UTF-8");
		}
		// 随机生成一个state，放入session中
		Random random = new Random();
		int state = random.nextInt();
		setSessionAttr(SESSION_STATE, Integer.toString(state));
		String uri = "https://account.xiaomi.com/oauth2/authorize";
		redirect(uri + "?client_id=" + Profiles.getMiAppId() + "&redirect_uri=" + redirectUri
				+ "&response_type=code&state=" + state);

	}

	/**
	 * Auth2.0授权回调地址，目前只用于登录，所有代码一个方法了，可能有点乱，后面会整理的
	 * 
	 * @throws IOException
	 */
	@ClearLoginInterceptor
	public void login() throws IOException {
		if (isParaExists("error")) {
			renderText("调用小米接口失败，错误码：" + getPara("error") + "，描述：" + getPara("error_description"));
			return;
		}
		String state = getPara("state");
		if (!state.equals(getSessionAttr(SESSION_STATE))) {
			renderError(403);
			return;
		}
		removeSessionAttr(SESSION_STATE);
		String code = getPara("code");
		JSONObject json = getAccessToken(code, getUrlPrefix() + "/mi/login");
		// 获取用户资料，然后登录或者注册
		JSONObject data = getUserData(json.getString("access_token"), json.getString("mac_key"));
		User user = User.dao.findByMiUserId(data.getInteger("userId"));
		if (user == null) {
			user = new User();
			user.set("id", UUIDKit.generateUUID());
			user.set("miUserId", data.getInteger("userId"));
			user.set("registerTime", new Date());
			user.set("avatarUrl", data.getString("miliaoIcon"));
			user.set("nickname", UserKit.makeUniqueNikname(data.getString("miliaoNick")));
			user.set("loginTime", new Date());
			user.set("loginIp", getClientIp());
			user.set("role", User.ROLE_MEMBER);
			user.set("sessionId", getSession().getId());
			user.save();
		} else if (!user.getBoolean("active")) {
			renderText("抱歉，您的帐号已经被管理员封禁，不能完成登录");
			return;
		} else {
			SessionManager.invalidate(user.getStr("sessionId"));
			user.set("loginTime", new Date());
			user.set("loginIp", getClientIp());
			user.set("sessionId", getSession().getId());
			user.keep("id", "loginTime", "loginIp", "sessionId").update();
			user = User.dao.findById(user.getStr("id"));
		}
		setSessionAttr(Const.SESSION_USER, user);
		String referer = getSessionAttr(Const.SESSION_REFERER);
		if (referer != null) {
			removeSessionAttr(Const.SESSION_REFERER);
			redirect(referer);
			return;
		}
		redirect("/");

	}

	/**
	 * 绑定
	 * 
	 * @throws IOException
	 */
	public void bind() throws IOException {
		if (isParaExists("error")) {
			renderText("调用小米接口失败，错误码：" + getPara("error") + "，描述：" + getPara("error_description"));
			return;
		}
		String state = getPara("state");
		if (!state.equals(getSessionAttr(SESSION_STATE))) {
			renderError(403);
			return;
		}
		removeSessionAttr(SESSION_STATE);
		User user = getSessionAttr(Const.SESSION_USER);
		if (user.getInt("miUserId") != null) {
			renderText("抱歉，您已经绑定过小米帐号了，不能重复绑定");
			return;
		}
		String code = getPara("code");
		JSONObject json = getAccessToken(code, getUrlPrefix() + "/mi/bind");
		// 获取用户资料
		JSONObject data = getUserData(json.getString("access_token"), json.getString("mac_key"));
		user.set("miUserId", data.getInteger("userId"));
		user.keep("id", "miUserId").update();
		setSessionAttr(Const.SESSION_USER, User.dao.findById(user.getStr("id")));
		redirect("/me/openid");

	}

	/**
	 * 获取随机nonce值 64是随机数，后面32位是总的分钟数
	 * 
	 * @return
	 */
	private String generateNonce() {
		Random random = new Random();
		long n = random.nextLong();
		long m = System.currentTimeMillis() / (1000L * 60L);
		return n + ":" + m;
	}

	/**
	 * 获取签名值
	 * 
	 * @param data
	 * @param key
	 * @return
	 */
	private String encryptHMACSha1(String data, String key) {
		try {
			SecretKeySpec signingKey = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA1");
			Mac mac = Mac.getInstance("HmacSHA1");
			mac.init(signingKey);
			mac.update(data.getBytes("UTF-8"));
			return new String(Base64.encodeBase64(mac.doFinal()), "UTF-8");
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 获取accesstoken，返回参数示例及说明：<br>
	 * {
	 * 
	 * “access_token”: “access token value”,
	 * 
	 * “expires_in”: 360000,
	 * 
	 * “refresh_token”: “refresh token value”,
	 * 
	 * “scope”: “scope value”,
	 * 
	 * “token_type “: “mac”,
	 * 
	 * “mac_key “: “mac key value”,
	 * 
	 * “mac_algorithm”: ” HmacSha1″
	 * 
	 * } <br>
	 * 字段说明：<br>
	 * 1) access_token：获取的Access Token；
	 * 
	 * 2) expires_in：Access Token的有效期，以秒为单位；
	 * 
	 * 3) refresh_token：用于刷新Access Token ,有效时间为10年；
	 * 
	 * 4) scope：Access Token最终的访问范围，既用户实际授予的权限列表；
	 * 
	 * 5) token_type: access token的类型，type值为mac；
	 * 
	 * 6) mac_key: MAC加密算法对请求进行加密时的密钥；
	 * 
	 * 7) mac_algorithm:MAC加密的算法，目前只支持HmacSha1
	 * 
	 * @param code
	 * @return
	 * @throws IOException
	 */
	private JSONObject getAccessToken(String code, String redirectUri) throws IOException {
		// 获取令牌，这个接口返回json
		String url = "https://account.xiaomi.com/oauth2/token";
		Map<String, String> queryParas = new HashMap<String, String>();
		queryParas.put("client_id", Profiles.getMiAppId());
		queryParas.put("redirect_uri", redirectUri);
		queryParas.put("client_secret", Profiles.getMiAppSecret());
		queryParas.put("grant_type", "authorization_code");
		queryParas.put("code", code);
		queryParas.put("token_type", "mac");

		String jsonStr = HttpKit.get(url, queryParas);
		// 这个接口返回的结果不是真正的json格式数据，在前头加了&&&START&&&，要去掉才能转换为json
		jsonStr = jsonStr.replace("&&&START&&&", "");
		JSONObject json = JSON.parseObject(jsonStr);
		if (json.containsKey("error")) {
			logger.info("调用小米接口失败，错误码：" + json.getString("error") + "，描述：" + json.getString("error_description"));
			throw new RuntimeException("调用小米接口获取access token失败");
		}
		return json;
	}

	/**
	 * 获取用户资料，接口返回json数据示例及说明：<br>
	 * {
	 * 
	 * “result”: “ok”,
	 * 
	 * “description”: “成功”,
	 * 
	 * “data”: {
	 * 
	 * “miliaoNick”: “米聊昵称”,
	 * 
	 * “userId”: 米聊号,
	 * 
	 * “miliaoIcon”: 头像URL
	 * 
	 * },
	 * 
	 * “code”: 0
	 * 
	 * }<br>
	 * 
	 * 
	 * @param accessToken
	 * @param macKey
	 * @return
	 * @throws IOException
	 */
	private JSONObject getUserData(String accessToken, String macKey) throws IOException {
		String nonce = generateNonce();
		String str = nonce + "\nGET\nopen.account.xiaomi.com\n/user/profile\nclientId=" + Profiles.getMiAppId()
				+ "&token=" + accessToken + "\n";
		String mac = encryptHMACSha1(str, macKey);

		// 发请求获取小米用户名片
		String url = "https://open.account.xiaomi.com/user/profile";
		// query string
		Map<String, String> queryParas = new HashMap<String, String>();
		queryParas.put("clientId", Profiles.getMiAppId());
		queryParas.put("token", accessToken);
		// headers
		Map<String, String> headers = new HashMap<String, String>();
		headers.put("Authorization", "MAC access_token=\"" + accessToken + "\",nonce=\"" + nonce + "\",mac=\"" + mac
				+ "\"");

		JSONObject json = JSON.parseObject(HttpKit.get(url, queryParas, headers));
		if (!"ok".equals(json.getString("result"))) {
			logger.info("调用小米接口获取用户名片失败，错误码：" + json.getString("code") + "，描述：" + json.getString("description"));
			throw new RuntimeException("调用小米接口获取用户资料失败");
		}

		return json.getJSONObject("data");
	}
}
